/*********************************************************************************
 *      Copyright:  (C) 2020 makun<1394987689@qq.com>
 *                  All rights reserved.
 *
 *       Filename:  fifo_chat.c
 *    Description:  This file 
 *                 
 *        Version:  1.0.0(2020年03月20日)
 *         Author:  makun <1394987689@qq.com>
 *      ChangeLog:  1, Release initial version on "2020年03月20日 10时09分55秒"
 *                 
 ********************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <libgen.h>
#include <stdlib.h>




#define FIFO_FILE1 ".fifo_chat1"
#define FIFO_FILE2 ".fifo_chat2"

int stop = 0;
void sig_pipe(int signum)
{
    if(SIGPIPE == signum)
    {
        printf("get pipe broken signal and let programe exit\n");
        stop = 1;
    }
}
int main (int argc, char **argv)
{
    int        fd_fifo;
    int        fdw_fifo;
    int        rv;
    fd_set     rdset;
    char       buf[1024];
    int        mode =0;

    if( argc != 2 )
     {
         printf("Usage: %s [0/1]\n", basename(argv[0]));
         printf("This chat program need run twice, 1st time run with [0] and 2nd time with [1]\n");
         return -1;
     }
    mode =atoi(argv[1]);

    if( access(FIFO_FILE1,F_OK) )//access函数的作用检查.fifo_chat1文件是否存在
    {
        printf("FIFO file %s not exit and create\n", FIFO_FILE1);
        mkfifo(FIFO_FILE1,0666);//创建管道.fifo_chat1是一个普通的路径名，它是该GIGI的名字

    }
    if( access(FIFO_FILE2,F_OK) )
    {
        printf("FIFO file %s not exit and create\n", FIFO_FILE2);
        mkfifo(FIFO_FILE2,0666);
    }
    /* 管道是一种半双工的通信方式，什么是半双工的通信，只能从一端写，另一端读，而全双工两端都能读和写，我们可以创建两个管道来实现全双工的通信*/
    signal(SIGPIPE,sig_pipe);//安装管道破裂信号，默认动作是程序终止


     /*  这里以只读模式打开命名管道FIFO_FILE1的读端，默认是阻塞模式；如果命名管道的写端被不打开则open()将会一直阻 塞，
         所以另外一个进程必须首先以写模式打开该文件FIFO_FILE1，否则会出现死锁 */
    if( 0 == mode )
    {
        printf("start open %s for read and it will blocked untill write endpoint ppened..\n",FIFO_FILE1);
        if( (fd_fifo=open(FIFO_FILE1, O_RDONLY)) <0)
    {
        printf("open fifo %s for chat read endpoint failure\n",strerror(errno));
        return -1;
    }
    printf("start open %s for write...\n",FIFO_FILE2);
    if( (fdw_fifo=open(FIFO_FILE2,O_WRONLY)) < 0)
    {
        printf("open fifo %s for chat write endpoint failure\n",strerror(errno));
        return -2;
    }
    }

/*这里以只写模式打开命名管道FIFO_FILE1的写端，默认是阻塞模式。如果命名管道的读端不被打开open讲会一直阻塞因为前一个进程是先以读模式打开该管道文件的读端，所以这里必须先以写模式打开该文件的写端，否则会出现死锁*/
    else
    {
        printf("start open '%s' for write and it will blocked untill read endpoint opened...\n",FIFO_FILE1);
        if( (fdw_fifo=open(FIFO_FILE1, O_WRONLY)) <0 )
        {
            printf("open fifo %s for chat write endpoint failure\n", strerror(errno));
            return -3;
        }
        printf("start open %s for read..\n",FIFO_FILE2);

        if( (fd_fifo=open(FIFO_FILE2,O_RDONLY)) <0  )
        {
            printf("open fifo %s for chat read endpoint failure\n", strerror(errno));
            return -4;
        }
    }   
        printf("start chating with another program now, please input message now: \n");
        while( !stop)
        {
             FD_ZERO(&rdset);
             FD_SET(STDIN_FILENO,&rdset);
             FD_SET(fd_fifo,&rdset);
            //select多路复用监听标准输入和作为输入的命名管道读端
             rv =select(fd_fifo+1,&rdset,NULL,NULL,NULL);

             if( rv <= 0)
             {
                 printf("select timeout or errno:%s\n",strerror(errno));
                 continue;
             }
             //如果是作为输入的命名管道上有数据到来则从管道上读入数据并打印到标注输出上
             if(FD_ISSET(fd_fifo, &rdset) )
             {
                 memset(buf, 0, sizeof(buf));
                 rv=read(fd_fifo,buf,sizeof(buf));
                 if( rv <0 )
                 {
                     printf("read from fifo failure:%s\n",strerror(errno));
                     break;
                 }
                 else if( 0 == rv )//如果从管道上度到字节数为0，说明管道的写端关闭
                 {
                     printf("Another side of FIFO get closed and program will exit now\n");
                     break;
                 }
                 printf("<--%s", buf);
             }
             //如果标准输入上有数据到来，则从标准输入上读入数据后，讲数据写入到作为输出的命名管道上给另外一个进程
            if( FD_ISSET(STDIN_FILENO,&rdset) )
            {
                memset(buf, 0, sizeof(buf));
                fgets(buf, sizeof(buf),stdin);
                write(fdw_fifo,buf,strlen(buf));
            }
        }
}
